/**
 * Copyright 2010 Victor Egea Hernando
 *
 * RockScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 * 
 * RockScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with RockScript.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "vmachine.h"

#include "../corelib/corelib.h"
#include "instruction.h"
#include "vm_macros.h"
#include "opcodes.h"

#include <algorithm>
#include <sstream>

namespace paper
{

vmachine::vmachine( corelib::uint32 size ){
	// reset state
    reset();
}

vmachine::~vmachine(){

}

void vmachine::add_routine( void ){
    _aroutine = new routine( 1 );
    _routine.push_back( _aroutine );
}

void vmachine::set_hnd( vmachine_hnd &hnd ){
	_hnd = &hnd;
}

void vmachine::reset( void ){
	// clear classes
	for( corelib::uint32 it=0;it<_class.size();it++ ){
		// delete class object
		delete _class[it];
	}
	// clear class vector
	_class.clear();
	_class_map.clear();
	// reset flags
	_flags = debug_enabled; // standard execution
	// _flags = print_opcodetrace | debug_enabled; // debugging + opcode trace
	// _flags = jit_enabled | print_jit | print_gc; // JIT enabled & GC debug enabled
	// reset routines
    _routine.clear(); add_routine();
	
    // reset object list
    _objects0.clear();
    _objects1.clear();
    _objects2.clear();

	// load corelib classes
	load_core_header( *this ); load_core_lib( *this );

	// load base class indexes
	_object_cidx  = get_class("corelib.core.Object").first->idx();
	_string_cidx  = get_class("corelib.core.String").first->idx();
	_array_cidx   = get_class("corelib.core.Array").first->idx();
	_ibuffer_cidx = get_class("corelib.core.IBuffer").first->idx();
	_rbuffer_cidx = get_class("corelib.core.RBuffer").first->idx();
	_bbuffer_cidx = get_class("corelib.core.BBuffer").first->idx();
}

bool vmachine::load_sfunction( const corelib::ustring &vc, const corelib::ustring &vm  ){
    // load class and then load the method
	method *mp = corelib::null; vm_class *cp = corelib::null; corelib::uint32 index;
    __class_map::iterator cres = _class_map.find( &vm_class( vc, 0 ) );
    if ( cres!=_class_map.end() ){
        // set current class
        cp = _class[cres->second];
        // load method pointer
        if( cp->get_method_idx( vm, index ) ){
            // load method pointer
        	mp = cp->get_method( index );
            // check if it's static
            if ( !( mp->_flags & FLG_MTHD_STATIC ) ){
                C_M_ERROR( vc, vm, corelib::ustring("the method '") +
                	vc + "::" + vm + corelib::ustring("' is not static") );
            }
        }else{
            // the method was not found
            C_M_ERROR( vc, "xx", corelib::ustring("the method '") +
            	vm + corelib::ustring("' was not found") );
        }
    }else{
        // the class was not found
        C_M_ERROR( "xx", "xx", corelib::ustring("the class '") +
        	vc + corelib::ustring("' was not found") );
    }

    // alloc frame in stack
    _aroutine->alloc_frame( *mp );
    // set routine to state new
    _aroutine->_state = NEW;

	return( true );
}

__register &vmachine::argument( corelib::uint32 index ){
	return( _aroutine->_rsp[ index ] );
}

__register &vmachine::spreg( corelib::int32 index ){
	return( INDXZ( REG16_OFFSET( index ) ) );
}

bool vmachine::run( const corelib::ustring &vc, const corelib::ustring &vm  ){
	// load static funtion
	if( !load_sfunction( vc, vm ) ) return( false );

    // interpret opcodes
    while( _aroutine->_state != TERMINATED ) {
    	// collect method cycles
    	_aroutine->_mp->_cycles += 0.01;
        // garbage collector
        garbage_collector( *this, 1 );

		// fetch opcode
		_ci = instruction( OPCODES[_aroutine->_pc] );

		// print VM opcode trace
		if( FLAGS_TEST( _flags, debug_enabled ) ){
			// check for breakpoints, etc...
			// ...
			// print opcode trace
			if( FLAGS_TEST( _flags, print_opcodetrace ) ){
				std::cout << "   [" << _ci.str() << "]" << std::endl;
			}
		}

        // interpret opcode
        switch ( _ci._inst._op ) {
			// LOADING/SAVING OPCODES ---------------------------------------------
			case OP_MOVE:{
				RA = RB;
				break;
    		}case OP_SAVE:{
				RZ = RA;
				break;
			}case OP_LOAD:{
				RA = RZ;
				break;
			}case OP_LOADCK:{
				break;
			}case OP_LOADMK:{
				RA = _aroutine->_mp->_constant[OPZ];
				break;
			}case OP_LOADIA:{
				reginteger(RZ) = OPA;
				break;
			}case OP_LOADIZ:{
				reginteger(RA) = OPZ;
				break;
			// ARITHMETICAL INTEGER OPCODES --------------------------------------------
			}case OP_ADDI:{
				reginteger(RA) = reginteger(RB) + reginteger(RC);
				break;
			}case OP_SUBI:{
				reginteger(RA) = reginteger(RB) - reginteger(RC);
				break;
			}case OP_MULI:{
				reginteger(RA) = reginteger(RB) * reginteger(RC);
				break;
			}case OP_DIVI:{
				reginteger(RA) = reginteger(RB) / reginteger(RC);
				break;
			}case OP_MODI:{
				reginteger(RA) = reginteger(RB) % reginteger(RC);
				break;
			}case OP_BWANDI:{
				reginteger(RA) = reginteger(RB) & reginteger(RC);
				break;
			}case OP_BWORI:{
				reginteger(RA) = reginteger(RB) | reginteger(RC);
				break;
			}case OP_BWXORI:{
				reginteger(RA) = reginteger(RB) ^ reginteger(RC);
				break;
			}case OP_BWSHLI:{
				reginteger(RA) = reginteger(RB) << reginteger(RC);
				break;
			}case OP_BWSHRI:{
				reginteger(RA) = reginteger(RB) >> reginteger(RC);
				break;
			}case OP_BWUSHRI:{
				reginteger(RA) = ((corelib::uint32)reginteger(RB)) >> reginteger(RC);
				break;
			}case OP_ABSI:{
				reginteger(RA) = ( reginteger(RZ) < 0 ) ? -reginteger(RZ) : reginteger(RZ);
				break;
			}case OP_BWNOTI:{
				reginteger(RA) = ~reginteger(RZ);
				break;
			}case OP_INCI:{
				reginteger(RZ)++;
				break;
			}case OP_DECI:{
				reginteger(RZ)--;
				break;
			// CONDITIONAL OPCODES ------------------------------------------------
			}case OP_RCJMP:{
				if( _aroutine->_flags & RFLG_JUMP ){
					_aroutine->_pc += OPZ; continue;
				}
				break;
			}case OP_RJMP:{
				_aroutine->_pc += OPZ;
				continue;
			}case OP_NOT:{
				FLAGS_TOOGLE( _aroutine->_flags, RFLG_JUMP );
				break;
			}case OP_EQI:{
				SET_JUMP_FLAG( reginteger(RA)==reginteger(RB) );
				break;
			}case OP_EQR:{
				SET_JUMP_FLAG( regreal(RA)==regreal(RB) );
				break;
			}case OP_NEQI:{
				SET_JUMP_FLAG( reginteger(RA)!=reginteger(RB) );
				break;
			}case OP_NEQR:{
				SET_JUMP_FLAG( regreal(RA)!=regreal(RB) );
				break;
			}case OP_LTI:{
				SET_JUMP_FLAG( reginteger(RA)<reginteger(RB) );
				break;
			}case OP_LTR:{
				SET_JUMP_FLAG( regreal(RA)<regreal(RB) );
				break;
			}case OP_LEQI:{
				SET_JUMP_FLAG( reginteger(RA)<=reginteger(RB) );
				break;
			}case OP_LEQR:{
				SET_JUMP_FLAG( regreal(RA)<=regreal(RB) );
				break;
			}case OP_GTI:{
				SET_JUMP_FLAG( reginteger(RA)>reginteger(RB) );
				break;
			}case OP_GTR:{
				SET_JUMP_FLAG( regreal(RA)>regreal(RB) );
				break;
			}case OP_GEI:{
				SET_JUMP_FLAG( reginteger(RA)>=reginteger(RB) );
				break;
			}case OP_GER:{
				SET_JUMP_FLAG( regreal(RA)>=regreal(RB) );
				break;
			// METHOD OPCODES ----------------------------------------------------------
			}case OP_LOADARG:{
				ARG = RB;
				break;
		    }case OP_KCCALL:{
		    	bool jitted = false;
		    	if( !op_kccall( *this, OPZ, OPA, jitted ) ){
		    		return( false );
		    	}else if( !jitted ) continue;
		    	break;
		    }case OP_UCCALL:{
				continue;
			}case OP_CPPCODE:{
				if( !op_cppcode( *this ) ) return ( false );
				continue;
			}case OP_RETURN:{
				op_return( *this, RA );
				break;
			// OBJECT OPCODES -----------------------------------------------------------
			}case OP_NEW:{
				op_new( *this, OPZ, RA );
				break;
			}case OP_DELETE:{
				op_delete( *this, RA );
				break;
			}default:{
				std::stringstream ss; ss << "opcode:";
				ss << static_cast<corelib::int32>( _ci._inst._op );
				ss << ";unknown;pc:" << _aroutine->_pc;
				send_error( *this, ss.str() );
				return( false );
			}
        }
		// increment program counter
		_aroutine->_pc++;
    }

	return ( true );
}

corelib::pair<vm_class*, bool> vmachine::add_class( const corelib::ustring &str ){
	// create new class
    vm_class *nc = new vm_class( str, _class.size() );
    // insert class index if possible
    corelib::pair<__class_map::iterator,bool> res =
    	_class_map.insert(__class_map::pair_t( nc, _class.size() ));

    // create a new class
    if ( res.second==true ){
        // push class
        _class.push_back( nc );
        // return class
        return ( corelib::pair<vm_class*, bool>( nc, true ) );
    }
	// delete old class
	delete nc;
    // return class
    return( corelib::pair<vm_class*, bool>( _class[res.first->second], false ) );
}

const __register &vmachine::lret( void ) const{
	return( LRETREG );
}

corelib::int32 vmachine::get_class_idx( const corelib::ustring &str ){
    // create an instance and add class
    __class_map::iterator res = _class_map.find( &vm_class( str, 0 ) );
    // create a new method
    if ( res==_class_map.end() ){
        // return method index
        return( -1 );
    }
    // return method index
    return ( (*res).second );
}

corelib::pair<vm_class*, bool> vmachine::get_class( const corelib::ustring &str ){
    // create an instance and add class
    __class_map::iterator res = _class_map.find( &vm_class( str, 0 ) );
    // create a new method
    if ( res==_class_map.end() ){
        // return method index
        return( corelib::pair<vm_class*, bool>( corelib::null, false ) );
    }
    // return method index
    return( corelib::pair<vm_class*, bool>(_class[(*res).second], true) );
}

};
